/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.skatiger.jrove.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.el.ELContextListener;
import javax.el.ELResolver;
import javax.el.ExpressionFactory;

import org.skatiger.jrove.Configurator;
import org.skatiger.jrove.ExternalContext;
import org.skatiger.jrove.JroveException;
import org.skatiger.jrove.ResourceResolver;
import org.skatiger.jrove.TagLibrary;
import org.skatiger.jrove.ViewContext;
import org.skatiger.jrove.config.ConfigProperty;
import org.skatiger.jrove.impl.jaxb.JaxbConfigReader;
import org.skatiger.jrove.util.ClassUtil;
import org.skatiger.jrove.util.ServiceLoaderUtil;
import org.skatiger.jrove.util.StringUtils;

public class DefaultConfigurator implements Configurator {

    private List<ELContextListener> _elContextListeners;
    private List<ELResolver> _elResolvers;
    private String[] _libraries = new String[0];
    private String[] _decorators = new String[0];
    private ExpressionFactory _expressionFactory;
    private ResourceResolver resolver;
    private String _expressionFactoryClass;
    private JaxbConfigReader configReader;
    private Map<String, ConfigProperty<? extends Serializable>> properties;

    public void setExpressionFactoryClass(String expressionFactoryClass) {
        this._expressionFactoryClass = expressionFactoryClass;
    }

    private DefaultConfigurator() {
        _elContextListeners = new ArrayList<ELContextListener>();
        _elResolvers = new ArrayList<ELResolver>();
        properties = new HashMap<String, ConfigProperty<? extends Serializable>>();
    }

    public DefaultConfigurator(ResourceResolver resolver) {
        this();
        this.resolver = resolver;
    }

    public void addELContextListener(ELContextListener listener) {
        synchronized (_elContextListeners) {
            _elContextListeners.add(listener);
        }
    }

    public void addELResolver(ELResolver resolver) {
        if (_elResolvers == null) {
            _elResolvers = new ArrayList<ELResolver>();
        }
        _elResolvers.add(resolver);

    }

    public ELContextListener[] getELContextListeners() {
        return _elContextListeners
                .toArray(new ELContextListener[_elContextListeners.size()]);
    }

    public ExpressionFactory getExpressionFactory() {
        if (_expressionFactory == null) {
            _autoconfigExpressionFactory();
        }
        return _expressionFactory;
    }

    public ResourceResolver getResourceResolver() {
        return resolver;
    }

    public void removeELContextListener(ELContextListener listener) {
        synchronized (_elContextListeners) {
            _elContextListeners.remove(listener);
        }
    }

    public void setResourceResolver(ResourceResolver resolver) {
        this.resolver = resolver;
    }

    public void removeELResolver(ELResolver resolver) {
        synchronized (_elResolvers) {
            _elResolvers.remove(resolver);
        }

    }

    public ELResolver[] getELResolvers() {
        return _elResolvers.toArray(new ELResolver[_elResolvers.size()]);
    }

    public void setLibraries(String[] _libraries) {
        this._libraries = _libraries;
    }

    public String[] getLibraries() {
        return _libraries;
    }

    public void setTagDecorators(String[] _decorators) {
        this._decorators = _decorators;
    }

    public String[] getTagDecorators() {
        return _decorators;
    }

    // auto config javax.el.ExpressionFactory
    private void _autoconfigExpressionFactory() {
        if (StringUtils.isNotBlank(_expressionFactoryClass)) {
            _createExpressionFactoryByClassName();
        } else {
            _createExpressionFactoryUseServiceLoader();
        }
    }

    private void _createExpressionFactoryUseServiceLoader() {
        _expressionFactory = ServiceLoaderUtil
                .loadService(ExpressionFactory.class);
    }

    @SuppressWarnings("unchecked")
    private void _createExpressionFactoryByClassName() {
        try {
            _expressionFactory = ClassUtil
                    .newInstance((Class<ExpressionFactory>) _getExpressionFactoryClass());
        } catch (InstantiationException e) {
            throw new JroveException(e);
        } catch (IllegalAccessException e) {
            throw new JroveException(e);
        }

    }

    private Class<?> _getExpressionFactoryClass() {
        try {
            return Thread.currentThread().getContextClassLoader()
                    .loadClass(_expressionFactoryClass);
        } catch (ClassNotFoundException e) {
            throw new JroveException(
                    "class ExpressionFactory implemention not found", e);
        }
    }

    private JaxbConfigReader getConfigReader() {
        if (configReader == null) {
            configReader = new JaxbConfigReader();
            try {
                configReader.init(this);
            } catch (Exception e) {
                throw new JroveException("read configuration exception.", e);
            }
        }
        return configReader;
    }

    public _RendererProvider getRendererProvider() {
        return getConfigReader().getRendererProvider();
    }

    public _ComponentProvider getComponentProvider() {
        return getConfigReader().getComponentProvider();
    }

    public List<TagLibrary> getTagLibraries() {
        return getConfigReader().getTagLibraries();
    }

    public Configurator setProperty(ConfigProperty<? extends Serializable> property) {
        this.properties.put(property.getKey(), property);
        return this;
    }

    public <T extends Serializable> T getProperty(ConfigProperty<T> property) {
        String key = property.getKey();
        if (properties.containsKey(key)) {
            return (T) properties.get(key).getValue();
        }
        return property.getValue();
    }

    public ViewContext createViewContext(ExternalContext externalContext) {
        DefaultViewContext vc = new DefaultViewContext(this, externalContext);
        return vc;
    }

}
